/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package cz.mzk.oai.metadataRepository.analysis;

import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;

/**
 *
 * @author Martin Řehánek (rehan at mzk.cz)
 */
public class Multiset<A> implements Collection<A> {

    private Map<A, Integer> valueOccuranceMap = new HashMap<A, Integer>();

    public boolean add(A value) {
        Integer counter = valueOccuranceMap.get(value);
        Integer newCounter = incrementCounter(counter);
        valueOccuranceMap.put(value, newCounter);
        return true;
    }

    private Integer incrementCounter(Integer counter) {
        if (counter == null) {
            return Integer.valueOf(1);
        } else {
            return Integer.valueOf(++counter);
        }
    }

    public int numberOfOccurence(A value) {
        Integer occurence = valueOccuranceMap.get(value);
        if (occurence == null) {
            return 0;
        } else {
            return occurence.intValue();
        }
    }

    public int size() {
        int size = 0;
        for (A value : valueOccuranceMap.keySet()) {
            size += valueOccuranceMap.get(value).intValue();
        }
        return size;
    }

    public boolean isEmpty() {
        return valueOccuranceMap.keySet().isEmpty();
    }

    public boolean contains(Object value) {
        return valueOccuranceMap.keySet().contains(value);
    }

    public Iterator<A> iterator() {
        return new Iterator<A>() {

            Iterator<A> valuesIterator = valueOccuranceMap.keySet().iterator();
            private A valueBeingProcessed = null;
            private int valueProcessedCounter;
            private A next = loadNext();

            private A loadNext() {
                if (valueBeingProcessed == null) {
                    //System.out.println("loading next value");
                    return loadNextValue();
                } else {
                    //System.out.println("loading next occurance");
                    return loadNextOccurance();
                }
            }

            private A loadNextValue() {
                //System.out.println("load next value");
                if (valuesIterator.hasNext()) {
                    valueBeingProcessed = valuesIterator.next();
                    valueProcessedCounter = 1;
                    return valueBeingProcessed;
                } else {
                    return null;
                }
            }

            private A loadNextOccurance() {
                //System.out.println("load next occurance");
                Integer total = valueOccuranceMap.get(valueBeingProcessed);
                //System.out.println("total:" + total);
                if (total.intValue() == valueProcessedCounter) {
                    valueBeingProcessed = null;
                    return loadNextValue();
                } else {
                    valueProcessedCounter++;
                    return valueBeingProcessed;
//                    System.out.println("counter: " + valueProcessedCounter);
//                    System.out.println("value: " + valueBeingProcessed);
                }
            }

            public boolean hasNext() {
                //System.out.println("hasNext: " + (next!= null));
                return next != null;
            }

            public A next() {
                if (hasNext()) {
                    A result = next;
                    next = loadNext();
                    return result;
                } else {
                    throw new NoSuchElementException();
                }
            }

            public void remove() {
                throw new UnsupportedOperationException("Not supported yet.");
            }
        };
    }

    public Object[] toArray() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public <T> T[] toArray(T[] a) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public boolean remove(Object o) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public boolean containsAll(Collection<?> c) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public boolean addAll(Collection<? extends A> c) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public boolean removeAll(Collection<?> c) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public boolean retainAll(Collection<?> c) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void clear() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("{");
        for (A value : valueOccuranceMap.keySet()) {
            for (int i = 0; i < valueOccuranceMap.get(value); i++) {
                builder.append(value);
                if (i != valueOccuranceMap.get(value) - 1) {
                    builder.append(",");
                }
            }
        }
        builder.append("}");
        return builder.toString();
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Multiset<A> other = (Multiset<A>) obj;
        if (this.valueOccuranceMap != other.valueOccuranceMap && (this.valueOccuranceMap == null || !this.valueOccuranceMap.equals(other.valueOccuranceMap))) {
            return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 97 * hash + (this.valueOccuranceMap != null ? this.valueOccuranceMap.hashCode() : 0);
        return hash;
    }

    public Map<A, Integer> getValueOccurenceMap() {
        return valueOccuranceMap;
    }
}
